package app

import (
	"context"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"log"
	"longmen/server/pkg/common/app/etcd"
	"longmen/server/pkg/common/app/server"
	"longmen/server/pkg/common/app/utils"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"
)

var (
	ServerError = errors.New("please init services")
)

type Option func(o *options)

type options struct {
	id        string
	name      string
	version   string
	endpoints string

	sigs     []os.Signal
	register *etcd.Registry
	service  *server.Server
}

func ID(id string) Option {
	return func(o *options) {
		o.id = id
	}
}

func Name(name string) Option {
	return func(o *options) {
		o.name = name
	}
}

func Version(ver string) Option {
	return func(o *options) {
		o.version = ver
	}
}

func Endpoint(endpoints string) Option {
	return func(o *options) {
		o.endpoints = endpoints
	}
}

func Register(r *etcd.Registry) Option {
	return func(o *options) {
		o.register = r
	}
}

func Server(server *server.Server) Option {
	return func(o *options) {
		o.service = server
	}
}

type App struct {
	opts options
	// TODO context
	ctx      context.Context
	cancel   func()
	mu       sync.Mutex
	instance *etcd.ServiceInstance
}

func NewApp(opts ...Option) *App {
	opt := options{
		id:   uuid.New().String(),
		sigs: []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
	}
	for _, o := range opts {
		o(&opt)
	}
	ctx, cancel := context.WithCancel(context.Background())
	res := &App{
		opts:   opt,
		ctx:    ctx,
		cancel: cancel,
		mu:     sync.Mutex{},
	}
	return res
}

func (a *App) Start() error {
	a.buildInstance()
	if a.opts.service == nil {
		return ServerError
	}

	go func() {
		if err := a.opts.service.Start(); err != nil {
			log.Fatalln(err)
		}
	}()
	if a.opts.register != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		if err := a.opts.register.Register(ctx, a.instance); err != nil {
			return err
		}
	}
	c := make(chan os.Signal, 1)
	signal.Notify(c, a.opts.sigs...)
	<-c
	return a.Stop()
}

func (a *App) Stop() error {
	if a.opts.register != nil && a.instance != nil {
		if err := a.opts.register.Unregister(context.Background(), a.instance); err != nil {
			return err
		}
	}
	if a.cancel != nil {
		a.cancel()
	}
	return nil
}

func (a *App) buildInstance() {
	s := a.opts.service
	endpoint, err := utils.Extract(s.GetAddress(), s.GetLn())
	if err != nil {
		log.Fatalf("get real ip is error: %+v \n", err.Error())
	}
	fmt.Println("endpoint: " + endpoint)
	a.instance = &etcd.ServiceInstance{
		ID:        a.opts.id,
		Name:      a.opts.name,
		Version:   a.opts.version,
		Endpoints: endpoint,
	}
}
